// Include basic c stuff
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

// Include local JNI interface stub
#include "PcapInterface.h"

// Local defines
#define BUFFERLENGTH 4096

// Exported JNI stubs in InterfaceFactory implementation
JNIEXPORT jobjectArray JNICALL Java_se_ytterman_jpcap_jni_PcapInterfaceFactory_getInterfaceArray
  (JNIEnv * env, jobject obj) 
{
  // Device list
  static char *devices[4] = {"APA", "BEPA", "CEPA", NULL};

  // "Error" buffer
  char errbuf[256];

  // "Retrieve" the device list
  if(0 == 1) {
    // Throw exception
    char msgBuffer[1024];
    sprintf(msgBuffer, "Unable to list interface array: pcap_findalldevs: %s", errbuf);
    jclass jnicPcapInterfaceException = env->FindClass("se/ytterman/jpcap/jni/PcapInterfaceFactoryException");
    jint throwSuccessful = env->ThrowNew(jnicPcapInterfaceException, msgBuffer);
  }
  
  // Create PcapInterface class reference
  jclass jnicPcapInterface = env->FindClass("se/ytterman/jpcap/jni/PcapInterface");  
    
  // Create MACAddress class reference
  jclass jnicMACAddress = env->FindClass("se/ytterman/jpcap/data/ethernet/MACAddress");

  // Create IPV4Adress class reference
  jclass jnicIPV4Address = env->FindClass("se/ytterman/jpcap/data/ip/IPV4Address");

  // Create an array of PcapInterface objects
  jobjectArray objIfcArray = static_cast<jobjectArray>(env->NewObjectArray(count, jnicPcapInterface, NULL));

  // Create and bind PcapInterface objects into array
  unsigned ifc_index = 0;
  for(device = devices[0]; device != NULL; device = devices[ifc_index]) {
    // Fetch method ID for default constructor of PcapInterface
    jmethodID jnimidPcapInterfaceDefault = env->GetMethodID(jnicPcapInterface, "<init>", "()V");

    // Create object instance of PcapInterface
    jobject jnioPcapInterface = env->NewObject(jnicPcapInterface, jnimidPcapInterfaceDefault);

    // Fetch field ID of PcapInterface device name string
    jfieldID jnifidName = env->GetFieldID(jnicPcapInterface, "name", "Ljava/lang/String;");

    // Create field value (This is a String(Object))
    jstring jniStrName = env->NewStringUTF(device->name);

    // Link String object reference into PcapInterface object
    env->SetObjectField(jnioPcapInterface, jnifidName, jniStrName);
    
    // Fetch field ID of PcapInterface device description string
    jfieldID jnifidDescription = env->GetFieldID(jnicPcapInterface, "description", "Ljava/lang/String;");

    // Create field value (This is a String(Object))
    jstring jniStrDescription;

    if(device->description == NULL)
      jniStrDescription = env->NewStringUTF("N/A");
    else
      jniStrDescription = env->NewStringUTF(device->description);

    // Link String object reference into PcapInterface object
    env->SetObjectField(jnioPcapInterface, jnifidDescription, jniStrDescription);

    // XXX Fetch MAC address of this interface using ioctl's not very portable,
    // ugly memory management, throw exception on error...
    u_char mac_buffer[6];
    u_char* ifc_mac = getLinuxMacAddress(device->name, mac_buffer);

    if(ifc_mac == NULL) {
      // Throw exception
      char msgBuffer[1024];
      sprintf(msgBuffer, "Unable to list interface array: ioctl: SIOCGIFHWADDR: %s", device->name);
      jclass jnicPcapInterfaceException = env->FindClass("se/ytterman/jpcap/jni/PcapInterfaceFactoryException");
      jint throwSuccessful = env->ThrowNew(jnicPcapInterfaceException, msgBuffer);
    }
    else {
      // Create new byte array java object for the MAC (6 bytes)
      jbyteArray jniarrMACBuffer = env->NewByteArray(6);

      // Copy the MAC address into the PcapInterface byte array
      jbyte *jnibMACBuffer = env->GetByteArrayElements(jniarrMACBuffer, 0);
      memcpy(jnibMACBuffer, ifc_mac, (jsize)6);
      env->ReleaseByteArrayElements(jniarrMACBuffer, jnibMACBuffer, 0);

      // Fetch method ID for default constructor of MACAddress
      jmethodID jnimidMACAddress = env->GetMethodID(jnicMACAddress, "<init>", "([B)V");

      // Create a jvalue union pointer
      jvalue jnivalueMACBuffer;

      // Assign the correct part of the union
      jnivalueMACBuffer.l = jniarrMACBuffer;

      // Create object instance
      jobject jnioMACAddress = env->NewObject(jnicMACAddress, jnimidMACAddress, jnivalueMACBuffer);
    
      // Fetch field ID of PcapInterface MAC address object reference
      jfieldID jnifidMACAddress = env->GetFieldID(jnicPcapInterface, "macAddress", "Lse/ytterman/jpcap/data/ethernet/MACAddress;");

      // Set value of MAC address object reference in PcapInterface
      env->SetObjectField(jnioPcapInterface, jnifidMACAddress, jnioMACAddress);
    }

    // Fetch field ID of PcapInterface IP address array
    jfieldID jnifidIPV4Addresses = env->GetFieldID(jnicPcapInterface, "ipv4Addresses", "[Lse/ytterman/jpcap/data/ip/IPV4Address;");

    // Count number of assigned IP addresses
    count = 0;
    pcap_addr_t* address; 
    for(address = device->addresses; address != NULL; address = address->next)
      count++;

    // Create an array of IPV4Address objects
    jobjectArray objIPAddressArray = static_cast<jobjectArray>(env->NewObjectArray(count, jnicIPV4Address, NULL));

    // Create and bind IPV4 objects into array
    unsigned adr_index = 0;
    for(address = device->addresses; address != NULL; address = address->next) {
      // Fetch address to array storing IP address XXX THIS IS CHEATING!
      // FURTHER ANALYSIS OF THE USAGE OF THE sockaddr STRUCT IS NEEDED
      u_char* ifc_ip = (u_char*)(((address->addr)->sa_data) + 2);

      // Create new byte array java object for the IPV4 address (4 bytes)
      jbyteArray jniarrIPV4Buffer = env->NewByteArray(4);

      // Copy the MAC address into the PcapInterface byte array
      jbyte *jnibIPBuffer = env->GetByteArrayElements(jniarrIPV4Buffer, 0);
      memcpy(jnibIPBuffer, ifc_ip, (jsize)4);
      env->ReleaseByteArrayElements(jniarrIPV4Buffer, jnibIPBuffer, 0);

      // Fetch method ID for default constructor of MACAddress
      jmethodID jnimidIPV4AddressDefault = env->GetMethodID(jnicIPV4Address, "<init>", "([B)V");

      // Create a jvalue union pointer
      jvalue jnivalueIPV4Buffer;

      // Assign the correct part of the union
      jnivalueIPV4Buffer.l = jniarrIPV4Buffer;

      // Create object instance
      jobject jnioIPV4Address = env->NewObject(jnicIPV4Address, jnimidIPV4AddressDefault, jnivalueIPV4Buffer);

      // Add IPV4Address object to array
      env->SetObjectArrayElement(objIPAddressArray, adr_index, jnioIPV4Address);

      // Increment array adr_index
      adr_index++;
    }

    // Set IP address array in PcapInterface object
    env->SetObjectField(jnioPcapInterface, jnifidIPV4Addresses, objIPAddressArray);    

    // Finally, add PcapInterface object to array
    env->SetObjectArrayElement(objIfcArray, ifc_index, jnioPcapInterface);

    // Increment array ifc_index
    ifc_index++;

    char *device = alldevs[count];
  }
   
  // return reference
  return objIfcArray;
}

JNIEXPORT jobject JNICALL Java_se_ytterman_jpcap_jni_PcapInterface_getSocketOnInterface
  (JNIEnv * env, jobject jnioPcapInterface)
{
  // Create argument class reference
  jclass jnicPcapInterface = env->FindClass("se/ytterman/jpcap/jni/PcapInterface");

  // Fetch name field ID
  jfieldID jnifidName = env->GetFieldID(jnicPcapInterface, "name", "Ljava/lang/String;");

  // Fetch name field object reference (String)
  jstring jnisName = static_cast<jstring>(env->GetObjectField(jnioPcapInterface, jnifidName));

  // Create C style string XXX THIS IS CHEATING! 
  // NO PROPER UTF ENCODING FOR NON ASCII CHARACTERS TAKE PLACE! 
  // NO TERMINATION OF STRING TAKE PLACE!
  const char* cstr_name = env->GetStringUTFChars(jnisName, NULL);

  // Open PCAP socket
  char errbuf[PCAP_ERRBUF_SIZE];
  pcap_t *pcap_t_ptr =  
    pcap_open_live(
		   cstr_name,  // name of the device
		   65536,    // portion of the packet to capture. 
		             // 65536 guarantees that the whole packet will be captured on all the link layers
		             // PCAP_OPENFLAG_PROMISCUOUS, promiscuous mode is nonzero
		   1,
		   1000,             // read timeout
		   errbuf            // error buffer
		   );
  
  // DEBUG
#ifdef DEBUG
  printf("InterfaceFactory.getSocketOnInterface: %lld\n", pcap_t_ptr);
#endif

  // Do error checking
  if(pcap_t_ptr == NULL) {
    // XXX Throw exception
  }
   
  // Release string
  env->ReleaseStringUTFChars(jnisName, cstr_name);

  // Create return class reference
  jclass jnicPcapSocket = env->FindClass("se/ytterman/jpcap/jni/PcapSocket");  

  // Create return object constructor method id
  jmethodID jnimidDefault = env->GetMethodID(jnicPcapSocket, "<init>", "()V");
    
  // Create return instance
  jobject jnioPcapSocket = env->NewObject(jnicPcapSocket, jnimidDefault);  

  // Fetch field "pointer" field ID
  jfieldID jnifidPcapPtr = env->GetFieldID(jnicPcapSocket, "pcap_t_ptr", "J");

  // Set pointer value
  env->SetLongField(jnioPcapSocket, jnifidPcapPtr, (jlong)pcap_t_ptr);

  // Fetch field containing parent interface object reference
  jfieldID jnifidParentIfc = env->GetFieldID(jnicPcapSocket, "parent_ifc", "Lse/ytterman/jpcap/jni/PcapInterface;");

  // Set parent interface object reference
  env->SetObjectField(jnioPcapSocket, jnifidParentIfc, jnioPcapInterface);

  // Return reference
  return jnioPcapSocket;
}

// Exported JNI stubs in PcapSocket implementation
JNIEXPORT void JNICALL Java_se_ytterman_jpcap_jni_PcapSocket_setFilter
  (JNIEnv *env, jobject jnioPcapSocket, jstring jnisFilter) 
{
  // Create return class reference
  jclass jnicPcapSocket = env->FindClass("se/ytterman/jpcap/jni/PcapSocket");  

  // Fetch field ID
  jfieldID jnifidPcapPtr = env->GetFieldID(jnicPcapSocket, "pcap_t_ptr", "J");

  // Get value
  pcap_t *pcap_t_ptr = (pcap_t *)env->GetLongField(jnioPcapSocket, jnifidPcapPtr);

  // Apply filter to socket
  if(pcap_t_ptr != NULL) {
    int res;
    struct bpf_program pgm;

    // Extract filter string
    
    // Create C style string XXX THIS IS CHEATING! 
    // NO PROPER UTF ENCODING FOR NON ASCII CHARACTERS TAKE PLACE! 
    // NO TERMINATION OF STRING TAKE PLACE!
    const char* cstr_filter = env->GetStringUTFChars(jnisFilter, NULL);

    // Compile PCAP filter program
    res = pcap_compile(pcap_t_ptr, &pgm, (char*)cstr_filter, 1, 0);
    if(res == -1) {
      // Compilation error XXX Throw
    }
    
    // Release string
    env->ReleaseStringUTFChars(jnisFilter, cstr_filter);
  
    // Apply filter
    res = pcap_setfilter(pcap_t_ptr, &pgm);
    if(res == -1) {
      // Filter application error XXX Throw
    }    
  }
  else {
    // No PCAP socket reference XXX Throw
  }
}

JNIEXPORT void JNICALL Java_se_ytterman_jpcap_jni_PcapSocket_read
  (JNIEnv *env, jobject jnioPcapSocket, jobject jnioPcapPacket)
{
  // Create return class reference
  jclass jnicPcapSocket = env->FindClass("se/ytterman/jpcap/jni/PcapSocket");  

  // Fetch field ID
  jfieldID jnifidPcapPtr = env->GetFieldID(jnicPcapSocket, "pcap_t_ptr", "J");

  // Get value
  pcap_t *pcap_t_ptr = (pcap_t *)env->GetLongField(jnioPcapSocket, jnifidPcapPtr);

  // DEBUG
#ifdef DEBUG
  printf("PcapSocket.read: %lld\n", pcap_t_ptr);
#endif

  // Check socket interface
  if(pcap_t_ptr != NULL) {
    // Loop termination flag 
    bool terminate = false;

    // Packet buffer control
    long long timestamp = 0;
    unsigned length = 0;
    u_char buffer[BUFFERLENGTH];

    // Loop until the entire packet is captured
    while(!terminate) {
      // Pcap result and packet information
      int res;
      struct pcap_pkthdr *header;
      const u_char *pkt_data;

      // Wait for and read packet from pcap
      res = pcap_next_ex(pcap_t_ptr, &header, &pkt_data);

      // Check result
      if(res > 0) {
	// XXX Timstamp handling is presently unclear
	// timestamp = ((header->ts.tv_sec * 1000) + (header->ts.tv_usec / 1000));

	// If we have a new data segment present
	if(header->caplen > 0) {

	  // Check for buffer overflow
	  if((length + header->caplen) <= BUFFERLENGTH) {
	    // Copy 
	    memcpy(&buffer[length], pkt_data, header->caplen);

	    // Calculate buffer pointer
	    length += header->caplen;

	    // Check for complete packet
	    terminate = (header->len == length);
	  }
	  else {
	    // XXX Throw
	  }
	}
      }
      else if(res == 0) {
	// Timeout XXX Throw or return
      }
      else if(res == -1) {
	// Error XXX Throw
      }
      else if(res == -2) {
	// EOF XXX Throw
      }
    }

    // Packet finished
#ifdef DEBUG
    pduprint(buffer, 12);
#endif

    // Create new byte array java object
    jbyteArray jniarrBuffer = env->NewByteArray(length);

    // Copy the contents into the array
    jbyte *jnibBuffer = env->GetByteArrayElements(jniarrBuffer, 0);
    memcpy(jnibBuffer, buffer, (jsize)length);
    env->ReleaseByteArrayElements(jniarrBuffer, jnibBuffer, 0);

    // Create return class reference
    jclass jnicPcapPacket = env->FindClass("se/ytterman/jpcap/jni/PcapPacket");

    // Fetch field ID
    jfieldID jnifidBuffer = env->GetFieldID(jnicPcapPacket,"buffer", "[B");

    // Set value
    env->SetObjectField(jnioPcapPacket, jnifidBuffer, jniarrBuffer);
  }
  else {
    // No socket reference XXX Throw
  }
}

JNIEXPORT void JNICALL Java_se_ytterman_jpcap_jni_PcapSocket_write
  (JNIEnv *env, jobject jnioPcapSocket, jobject jnioPcapPacket)
{
  // Create return class reference
  jclass jnicPcapSocket = env->FindClass("se/ytterman/jpcap/jni/PcapSocket");  

  // Fetch field ID
  jfieldID jnifidPcapPtr = env->GetFieldID(jnicPcapSocket, "pcap_t_ptr", "J");

  // Get value
  pcap_t *pcap_t_ptr = (pcap_t *)env->GetLongField(jnioPcapSocket, jnifidPcapPtr);

  // DEBUG
#ifdef DEBUG
  printf("PcapSocket.write: %lld\n", pcap_t_ptr);
#endif

  // Check socket reference
  if(pcap_t_ptr != NULL) {
    // Create return class reference
    jclass jnicPcapPacket = env->FindClass("se/ytterman/jpcap/jni/PcapPacket");

    // Fetch field ID
    jfieldID jnifidBuffer = env->GetFieldID(jnicPcapPacket,"buffer", "[B");

    // Get array object
    jbyteArray jniarrBuffer = static_cast<jbyteArray>(env->GetObjectField(jnioPcapPacket, jnifidBuffer));

    // Get array length
    jsize length = env->GetArrayLength(jniarrBuffer);

    // Fetch buffer payload XXX boundary checking!
    u_char buffer[BUFFERLENGTH];
    jbyte *jnibBuffer = env->GetByteArrayElements(jniarrBuffer, 0);
    memcpy(buffer, jnibBuffer, length);
    env->ReleaseByteArrayElements(jniarrBuffer, jnibBuffer, 0);

    // Send packet
    int res;
    res = pcap_sendpacket(pcap_t_ptr, buffer, length);

    if(res == -1) {
      // XXX Throw exception if a problem occurs
    }
  }
  else {
    // No PCAP socket reference XXX Throw
  }
}

JNIEXPORT void JNICALL Java_se_ytterman_jpcap_jni_PcapSocket_close
  (JNIEnv *env, jobject jnioPcapSocket) 
{
  // Create return class reference
  jclass jnicPcapSocket = env->FindClass("se/ytterman/jpcap/jni/PcapSocket");  

  // Fetch field ID
  jfieldID jnifidPcapPtr = env->GetFieldID(jnicPcapSocket, "pcap_t_ptr", "J");

  // Get value
  pcap_t *pcap_t_ptr = (pcap_t *)env->GetLongField(jnioPcapSocket, jnifidPcapPtr);

  // DEBUG
#ifdef DEBUG
  printf("PcapSocket.close: %lld\n", pcap_t_ptr);
#endif

  // Release allocated resources
  if(pcap_t_ptr != NULL)
    pcap_close(pcap_t_ptr);

  // Set pointer to NULL
  pcap_t_ptr = NULL;
}

